package com.wei.czz.framework.admin.manager;

import com.alibaba.fastjson2.JSON;
import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.constant.RegexConstant;
import com.wei.czz.common.dto.admin.menu.*;
import com.wei.czz.common.dto.common.NameValue;
import com.wei.czz.common.dto.common.UploadDto;
import com.wei.czz.common.easyexcel.listener.ObjectUploadListener;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.enums.common.exportTask.TaskTypeEnum;
import com.wei.czz.common.enums.admin.menu.MenuDisplayEnum;
import com.wei.czz.common.enums.admin.menu.MenuTypeEnum;
import com.wei.czz.common.enums.common.exportTask.ExportStatusEnum;
import com.wei.czz.common.enums.common.importTask.ImportTaskTypeEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.po.UserPo;
import com.wei.czz.common.po.admin.userRole.UserRolePo;
import com.wei.czz.common.tool.PageDto;
import com.wei.czz.common.utils.*;
import com.wei.czz.common.vo.admin.menu.MenuUploadVo;
import com.wei.czz.common.vo.common.ChoiceExportVo;
import com.wei.czz.framework.admin.entity.RoleEntity;
import com.wei.czz.framework.admin.helper.UserHelper;
import com.wei.czz.framework.admin.service.MenuService;
import com.wei.czz.framework.admin.service.RoleMenuService;
import com.wei.czz.framework.admin.service.RoleService;
import com.wei.czz.framework.common.entity.ExportTaskEntity;
import com.wei.czz.framework.common.helper.ImportTaskHelper;
import com.wei.czz.framework.common.service.ExportTaskService;
import com.wei.czz.framework.common.service.SnowflakeService;
import com.wei.czz.framework.common.handler.ThrottleHandler;
import com.wei.czz.framework.common.handler.redis.RedisHashHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.common.vo.admin.menu.MenuVo;
import com.wei.czz.common.vo.common.UpdateStatusVo;
import com.wei.czz.common.vo.admin.menu.MenuFormVo;
import com.wei.czz.framework.admin.entity.MenuEntity;
import com.wei.czz.framework.common.helper.CacheHelper;
import com.wei.czz.framework.common.service.AsyncService;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-12-07 14:55:52
 * className: MenuManager
 * version: 1.0
 * description:
 */
@Component
@AllArgsConstructor
public class MenuManager {

    private static final Logger log = LoggerFactory.getLogger(MenuManager.class);

    private final MenuService menuService;

    private final RoleService roleService;

    private final RoleMenuService roleMenuService;

    private final ExportTaskService exportTaskService;

    private final RedisHashHandler redisHashHandler;

    private final RedisStringHandler redisStringHandler;

    private final RedisHandler redisHandler;

    private final RedissonClient redissonClient;

    private final AsyncService asyncService;

    private final CacheHelper cacheHelper;
    
    private final ThrottleHandler throttleHandler;

    private final SnowflakeService snowflakeService;

    private final UserHelper userHelper;

    private final ImportTaskHelper importTaskHelper;

    @Transactional
    public void saveMenu(MenuVo menuVo) {
        // 获取正在执行菜单添加的用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        /*
            保存菜单
         */
        MenuEntity menu = CopyUtils.map(menuVo, MenuEntity.class);
        // 设置菜单主键、父级菜单主键、更新时间、操作更新的用户id、创建时间和操作创建的用户id属性值
        menu.setMenuId(null)
                .setMenuPath(menuVo.getMenuPath() + menuVo.getParentId() + Constant.SPLIT)
                .setPath(Optional.ofNullable(menu.getPath()).orElse(StringUtils.EMPTY))
                .setPermit(Optional.ofNullable(menu.getPermit()).orElse(StringUtils.EMPTY))
                .setIcon(Optional.ofNullable(menu.getIcon()).orElse(StringUtils.EMPTY))
                .setStatus(CommonEnum.ZERO.getValue())
                .setDeleteStatus(CommonEnum.ZERO.getValue())
                .setUpdateTime(date)
                .setUpdateUser(StringUtils.EMPTY)
                .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                .setCreateTime(date)
                .setCreateUser(userPo.getUsername())
                .setCreateUserId(userPo.getUserId());
        log.debug("插入菜单对象: {}", menu);
        menuService.saveOrEdit(menu);

        // 操作Redis缓存，新增菜单缓存信息
        redisHashHandler.put(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menu.getMenuId().toString(), menu);

        /*
            删除菜单信息的所有临时缓存数据
         */
        this.deleteMenuCache();
    }

    @Transactional
    public void batchSaveMenu(List<MenuVo> menuVoList) {
        // 获取正在批量保存菜单数据的用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        // 父级主键列表
        int size = menuVoList.size();
        List<Long> parentIdList = new ArrayList<>(size);
        for (MenuVo menuVo : menuVoList) {
            // 收集父级主键
            parentIdList.add(menuVo.getParentId());
        }

        // 等待入库的数据
        List<MenuEntity> insertList = new ArrayList<>();
        // 需要延后处理的菜单数据
        List<MenuVo> lastOptList = new ArrayList<>();

        /*
            获取菜单
         */
        List<MenuEntity> menuList = menuService.getList(parentIdList);
        // List 转 Map
        Map<Long, MenuEntity> parentMenuMap = new HashMap<>();
        if (!menuList.isEmpty()) {
            parentMenuMap = CopyUtils.listToMap(menuList, MenuEntity::getMenuId, Function.identity());
        }

        for (MenuVo menuVo : menuVoList) {
            // 获取父级菜单数据
            MenuEntity parentMenu = parentMenuMap.get(menuVo.getParentId());
            if (parentMenu == null && CommonEnum.ZERO.getLongValue().equals(menuVo.getParentId())) {
                // 如果父级菜单是顶级菜单
                parentMenu = new MenuEntity();
                parentMenu.setMenuId(CommonEnum.ZERO.getLongValue());
                parentMenu.setMenuPath(StringUtils.EMPTY);
            }
            if (parentMenu == null) {
                // 父级菜单数据为空，说明父级菜单在同批次保存数据中，延后处理
                lastOptList.add(menuVo);
                continue;
            }
            MenuEntity menu = this.getMenuEntity(menuVo, userPo, date);
            menu.setMenuPath(parentMenu.getMenuPath() + parentMenu.getMenuId() + Constant.SPLIT);

            insertList.add(menu);
            parentMenuMap.put(menuVo.getMenuId(), menu);
        }
        for (MenuVo menuVo : lastOptList) {
            // 获取父级菜单数据
            MenuEntity parentMenu = parentMenuMap.get(menuVo.getParentId());
            if (parentMenu == null) {
                log.info("同批次数据找不到其父级菜单数据。{}", menuVo);
                throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "同批次保存数据中，父级菜单不存在，请确认。");
            }
            MenuEntity menu = this.getMenuEntity(menuVo, userPo, date);
            menu.setMenuPath(parentMenu.getMenuPath() + parentMenu.getMenuId() + Constant.SPLIT);

            insertList.add(menu);
        }
        /*
            保存菜单
         */
        menuService.batchSaveMenu(insertList);

        // 数据映射
        Map<String, MenuEntity> menuMap = new HashMap<>();
        for (MenuEntity menu : insertList) {
            menuMap.put(menu.getMenuId().toString(), menu);
        }
        // 操作Redis缓存，批量新增菜单缓存信息
        redisHashHandler.putAll(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuMap);

        // 删除菜单信息的所有临时缓存数据
        this.deleteMenuCache();
    }

    public UploadDto<MenuUploadVo> uploadMenu(Boolean mustAll, ObjectUploadListener<MenuUploadVo> objectUploadListener) {
        // 获取操作用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        ImportTaskTypeEnum importTaskTypeEnum = ImportTaskTypeEnum.MENU;

        // 解析成功列表
        List<MenuUploadVo> successList = objectUploadListener.getSuccessList();
        // 解析成功映射
        Map<Integer, MenuUploadVo> successMap = objectUploadListener.getSuccessMap();
        // 解析失败列表
        List<MenuUploadVo> failList = objectUploadListener.getFailList();

        /*
            判断是否有可导入数据
         */
        if (successList.isEmpty()) {
            // 没有解析成功数据，直接返回
            return importTaskHelper.buildSyncUploadDto(importTaskTypeEnum, successList, failList, MenuUploadVo.class);
        }

        // 收集父级菜单主键
        List<Long> menuIdList = CopyUtils.mapList(successList, MenuUploadVo::getParentNo);

        /*
            获取父级菜单
         */
        List<MenuEntity> menuList = menuService.getList(menuIdList);
        // list 转 map
        Map<Long, MenuEntity> menuMap = CopyUtils.listToMap(menuList, MenuEntity::getMenuId, Function.identity());

        List<MenuEntity> saveList = new ArrayList<>();

        Map<String, MenuTypeEnum> typeNameMap = MenuTypeEnum.getNameMap();

        Iterator<MenuUploadVo> iterator = successList.iterator();
        while (iterator.hasNext()) {
            MenuUploadVo menuUploadVo = iterator.next();

            // 获取菜单类型
            MenuTypeEnum menuTypeEnum = typeNameMap.get(menuUploadVo.getTypeName());
            if (Objects.isNull(menuTypeEnum)) {
                log.info("菜单类型不存在。serial={} typeName={}", menuUploadVo.getSerial(),
                        menuUploadVo.getTypeName());
                menuUploadVo.setErrorMessage("菜单类型不存在");
                failList.add(menuUploadVo);
                iterator.remove();
                continue;
            }
            // 获取菜单显示状态
            MenuDisplayEnum menuDisplayEnum = MenuDisplayEnum.find(menuUploadVo.getDisplayName());
            if (Objects.isNull(menuDisplayEnum)) {
                log.info("菜单显示状态不存在。serial={} displayName={}", menuUploadVo.getSerial(),
                        menuUploadVo.getDisplayName());
                menuUploadVo.setErrorMessage("菜单显示状态不存在");
                failList.add(menuUploadVo);
                iterator.remove();
                continue;
            }

            String errorMessage = StringUtils.EMPTY;
            switch (menuTypeEnum) {
                case PACKAGE_TYPE:
                case PAGE_TYPE:
                    if (StringUtils.isBlank(menuUploadVo.getPath())) {
                        errorMessage = menuTypeEnum.getName() + "菜单，菜单路径不能为空";
                    } else if (StringUtils.isBlank(menuUploadVo.getIcon())) {
                        errorMessage = menuTypeEnum.getName() + "菜单，菜单图标不能为空";
                    }
                    break;
                case BUTTON_TYPE:
                    if (StringUtils.isBlank(menuUploadVo.getPermit())) {
                        errorMessage = menuTypeEnum.getName() + "菜单，权限标识不能为空";
                    } else if (Pattern.matches(RegexConstant.ROLE_KEY_REGEX, menuUploadVo.getPermit())) {
                        errorMessage = menuTypeEnum.getName() + "菜单，权限标识不符合规则";
                    }
                    break;
                default:
                    log.error("菜单类型校验逻辑未实现。value={} name={}", menuTypeEnum.getValue(), menuTypeEnum.getName());
                    errorMessage = "菜单类型校验逻辑未实现";
                    break;
            }
            if (StringUtils.isNotBlank(errorMessage)) {
                log.info("菜单数据填写错误。serial={} name={}", menuUploadVo.getSerial(), menuUploadVo.getName());
                menuUploadVo.setErrorMessage(errorMessage);
                failList.add(menuUploadVo);
                iterator.remove();
                continue;
            }

            // 从数据库存储的菜单中获取父级菜单
            MenuEntity parentMenu = menuMap.get(menuUploadVo.getParentNo());

            /*
                不是以下这三种情况，则判定该条数据错误
                1、父级菜单不是0
                2、父级菜单不是数据库已有的菜单
                3、父级菜单不是同一批上传的菜单（需父级菜单在正在遍历菜单的前面）
             */
            if (!CommonEnum.ZERO.getLongValue().equals(menuUploadVo.getParentNo()) && Objects.isNull(parentMenu)
                    && !successMap.containsKey(menuUploadVo.getParentNo().intValue())) {
                log.info("父级菜单不存在。serial={} parentNo={}", menuUploadVo.getSerial(),
                        menuUploadVo.getParentNo());
                menuUploadVo.setErrorMessage("父级菜单不存在");
                failList.add(menuUploadVo);
                iterator.remove();
                continue;
            }

            Long parentId;
            String parentPath;
            if (CommonEnum.ZERO.getLongValue().equals(menuUploadVo.getParentNo())) {
                parentId = CommonEnum.ZERO.getLongValue();
                parentPath = CommonEnum.ZERO.getValue() + Constant.SPLIT;

            } else if (Objects.nonNull(parentMenu)) {
                parentId = parentMenu.getMenuId();
                parentPath = parentMenu.getMenuPath() + parentMenu.getMenuId() + Constant.SPLIT;

            } else {
                log.info("父级菜单找不到。serial={} parentNo={}", menuUploadVo.getSerial(),
                        menuUploadVo.getParentNo());
                menuUploadVo.setErrorMessage("父级菜单找不到");
                failList.add(menuUploadVo);
                iterator.remove();
                continue;
            }

            MenuEntity menu = new MenuEntity();
            menu.setMenuId(snowflakeService.get())
                    .setParentId(parentId)
                    .setMenuPath(parentPath)
                    .setName(menuUploadVo.getName());
            switch (menuTypeEnum) {
                case PACKAGE_TYPE:
                case PAGE_TYPE:
                    menu.setPath(menuUploadVo.getPath())
                        .setPermit(StringUtils.EMPTY)
                        .setIcon(menuUploadVo.getIcon());
                    break;
                case BUTTON_TYPE:
                    menu.setPath(StringUtils.EMPTY)
                        .setPermit(menuUploadVo.getPermit())
                        .setIcon(StringUtils.EMPTY);
                    break;
                default:
                    log.error("根据菜单类型赋值逻辑未实现。value={} name={}", menuTypeEnum.getValue(), menuTypeEnum.getName());
                    errorMessage = "根据菜单类型赋值逻辑未实现";
                    break;
            }
            if (StringUtils.isNotBlank(errorMessage)) {
                log.info("功能缺失，缺少赋值逻辑。serial={} name={}", menuUploadVo.getSerial(), menuUploadVo.getName());
                menuUploadVo.setErrorMessage(errorMessage);
                failList.add(menuUploadVo);
                iterator.remove();
                continue;
            }
            menu.setType(menuTypeEnum.getValue())
                    .setSort(menuUploadVo.getSort())
                    .setDisplay(menuDisplayEnum.getValue())
                    .setStatus(CommonEnum.ZERO.getValue())
                    .setDeleteStatus(CommonEnum.ZERO.getValue())
                    .setUpdateTime(date)
                    .setUpdateUser(StringUtils.EMPTY)
                    .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                    .setCreateTime(date)
                    .setCreateUser(userPo.getUsername())
                    .setCreateUserId(userPo.getUserId());
            // 记录
            saveList.add(menu);
            // 增加新映射，方便下面使用
            menuMap.put(menuUploadVo.getSerial().longValue(), menu);
        }

        /*
            生成导入记录
         */
        UploadDto<MenuUploadVo> uploadDto = importTaskHelper.buildSyncUploadDto(importTaskTypeEnum, successList,
                failList, MenuUploadVo.class);

        if (saveList.isEmpty()) {
            log.info("映射成功数据为空");
            return uploadDto;
        }
        if (mustAll && !failList.isEmpty()) {
            log.info("存在解析错误数据，不保存解析成功数据");
            return uploadDto;
        }

        /*
            保存菜单
         */
        menuService.batchSaveMenu(saveList);

        // 数据映射
        Map<String, MenuEntity> cacheMenuMap = new HashMap<>();
        for (MenuEntity menu : saveList) {
            cacheMenuMap.put(menu.getMenuId().toString(), menu);
        }
        // 操作Redis缓存，批量新增菜单缓存信息
        redisHashHandler.putAll(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), cacheMenuMap);

        // 删除菜单信息的所有临时缓存数据
        this.deleteMenuCache();

        return uploadDto;
    }

    public PageDto<MenuDto> getPageMenuList(MenuFormVo menuVo) {
        // 获取请求用户主键
        Long optUserId = SecurityUtils.getUserId();

        /*
            缓存请求参数
         */
        String cacheKey = CacheKeyUtils.getParamCacheKey(EntityConstant.MENU);
        cacheHelper.cache(cacheKey, menuVo);

        String menuListKey = menuVo.getMenuListKey();

        // 查询Redis缓存，获取菜单列表缓存
        PageDto<MenuDto> pageDto = redisStringHandler.get(menuListKey);
        if (pageDto != null) {
            log.info("从redis缓存中得到菜单分页数据");
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(menuListKey, 150, RandomNumUtils.random5Minutes(10));
            }, menuListKey, 1500, TimeUnit.MILLISECONDS);
            return pageDto;
        }
        // 获取分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + menuListKey);
        // 加锁
        lock.lock(30, TimeUnit.SECONDS);

        try {
            pageDto = redisStringHandler.get(menuListKey);
            if (pageDto != null) {
                log.info("double check 从redis缓存中得到菜单分页数据");
                return pageDto;
            }
            
            /*
                分页查询菜单列表
             */
            PageDto<MenuEntity> menuPageDto = menuService.getPageMenuList(menuVo);
            if (menuPageDto.isEmpty()) {
                pageDto = menuPageDto.rebuild();
                // 缓存分页菜单数据五分钟
                redisStringHandler.set(menuVo.getMenuListKey(), pageDto, RandomNumUtils.random5Minutes(10),
                        TimeUnit.SECONDS);
                return pageDto;
            }

            List<MenuEntity> menuList = menuPageDto.getList();

            if (StringUtils.isNotBlank(menuVo.getWord())) {
                // 列表映射
                List<MenuDto> menuDtoList = CopyUtils.mapList(menuList, menu -> {
                    MenuDto menuDto = CopyUtils.map(menu, MenuDto.class);
                    menuDto.setTypeName(MenuTypeEnum.getName(menu.getType()))
                            .setDisplayName(MenuDisplayEnum.getName(menu.getDisplay()))
                            .setStatusName(CommonEnum.getStatusName(menu.getStatus()))
                            .setDisabled(Boolean.FALSE);
                    return menuDto;
                });

                // 分页数据封装
                pageDto = menuPageDto.rebuild(menuDtoList);

                // 缓存分页菜单数据五分钟
                redisStringHandler.set(menuListKey, pageDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return pageDto;
            }

            Map<String, MenuDto> menuMap = new HashMap<>();
            List<MenuDto> lastMenuList = new ArrayList<>();

            for (MenuEntity menu : menuList) {
                // 映射
                MenuDto menuDto = CopyUtils.map(menu, MenuDto.class);
                menuDto.setTypeName(MenuTypeEnum.getName(menu.getType()))
                        .setDisplayName(MenuDisplayEnum.getName(menu.getDisplay()))
                        .setStatusName(CommonEnum.getStatusName(menu.getStatus()))
                        .setDisabled(Boolean.FALSE);

                menuMap.put(menuDto.getMenuId(), menuDto);
                // 获取父菜单
                MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
                if (Objects.isNull(parentMenuDto)) {
                    // 延后处理
                    lastMenuList.add(menuDto);
                    continue;
                }

                TreeSet<MenuDto> children = parentMenuDto.getChildren();
                if (Objects.isNull(children)) {
                    children = new TreeSet<>();
                    parentMenuDto.setChildren(children);
                }
                children.add(menuDto);
            }
            List<MenuDto> resultMenuList = new ArrayList<>();
            for (MenuDto menuDto : lastMenuList) {
                // 获取父菜单
                MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
                if (Objects.isNull(parentMenuDto)) {
                    // 放入结果列表
                    resultMenuList.add(menuDto);
                    continue;
                }

                TreeSet<MenuDto> children = parentMenuDto.getChildren();
                if (Objects.isNull(children)) {
                    children = new TreeSet<>();
                    parentMenuDto.setChildren(children);
                }
                children.add(menuDto);
            }

            // 分页数据封装
            pageDto = menuPageDto.rebuild(resultMenuList);

            // 缓存分页菜单数据五分钟
            redisStringHandler.set(menuListKey, pageDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

            return pageDto;
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    public MenuPageEnumDto getMenuPageParam() {
        // 获取请求用户主键
        Long optUserId = SecurityUtils.getUserId();

        /*
            获取页面表单对象
         */
        String cacheKey = CacheKeyUtils.getParamCacheKey(EntityConstant.MENU);
        MenuFormVo menuFormVo = cacheHelper.get(cacheKey, MenuFormVo.class);

        /*
            获取一级菜单
         */
        List<MenuEntity> menuList = menuService.findList(Collections.singletonList(CommonEnum.ZERO.getLongValue()));
        List<NameValue<String>> oneParentMenuList = CopyUtils.mapList(
                menuList,
                menu -> new NameValue<>(menu.getName(), menu.getMenuId().toString())
        );

        MenuPageEnumDto menuPageEnumDto = new MenuPageEnumDto();
        menuPageEnumDto.setOneParentMenuList(oneParentMenuList)
                .setTypeList(MenuTypeEnum.getList())
                .setDisplayList(MenuDisplayEnum.getList())
                .setStatusList(CommonEnum.getStatusList())
                .setMenuVo(menuFormVo);
        return menuPageEnumDto;
    }

    public UserMenuDto getUserMenu() {

        // 获取当前正在获取自己可用菜单树操作的用户对象
        UserPo userPo = SecurityUtils.getUser();

        // 构造用户菜单树缓存键
        String cacheKey = RedisConstant.STRING + EntityConstant.USER + Constant.UNDER + EntityConstant.MENU
                + Constant.SPLIT + RedisConstant.TREE + userPo.getUserId();
        // 查询Redis缓存，获取用户菜单列表
        UserMenuDto userMenuDto = redisStringHandler.get(cacheKey);
        if (userMenuDto != null) {
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(cacheKey, 150, RandomNumUtils.random5Minutes(10));
            }, cacheKey, 1, TimeUnit.MINUTES);
            return userMenuDto;
        }

        List<MenuEntity> menuList;
        List<String> roleKeyList;

        // 判断用户是否超级管理员
        boolean superManager = userHelper.isSuperManager();
        if (superManager) {
            /*
                超级管理员
             */
            // 获取所有可用菜单
            menuList = menuService.getMenuList(CommonEnum.ZERO.getValue());
            // 获取所有可用角色
            List<RoleEntity> roleList = roleService.getList();

            roleKeyList = CopyUtils.mapList(roleList, RoleEntity::getRoleKey);
        } else {
            // 普通用户
            menuList = menuService.findList(userPo.getUserId());

            List<UserRolePo> userRolePoList = roleService.findList(userPo.getUserId(), CommonEnum.ZERO.getValue());
            roleKeyList = CopyUtils.mapList(userRolePoList, UserRolePo::getRoleKey);
        }

        Map<String, MenuDto> menuDtoMap = new HashMap<>();
        // 菜单树
        TreeSet<MenuDto> menuTree = new TreeSet<>();
        // 菜单数据封装
        List<MenuDto> pageMenuList = new ArrayList<>();
        // 权限字符串列表
        List<String> permitList = new ArrayList<>();
        // 需要延后处理的菜单
        List<MenuDto> lastOptMenuList = new ArrayList<>();
        // 构造过滤|失败菜单映射
        Map<Integer, StringBuilder> buildFailMenuMap = new HashMap<>();

        for (MenuEntity menu : menuList) {
            if (MenuTypeEnum.BUTTON_TYPE.getValue().equals(menu.getType())) {
                // ’按钮‘菜单
                String[] permits = menu.getPermit().split(",");
                Collections.addAll(permitList, permits);
                continue;
            }
            MenuDto menuDto = new MenuDto();
            menuDto.setMenuId(menu.getMenuId().toString())
                    .setParentId(menu.getParentId().toString())
                    .setName(menu.getName())
                    .setPath(menu.getPath())
                    .setType(menu.getType())
                    .setIcon(menu.getIcon())
                    .setSort(menu.getSort())
                    .setDisplay(menu.getDisplay())
                    .setCreateTime(menu.getCreateTime());
            // 存储菜单dto对象
            MenuDto put = menuDtoMap.put(menuDto.getMenuId(), menuDto);
            if (put != null) {
                // 去除重复菜单
                menuDtoMap.put(menuDto.getMenuId(), put);
                continue;
            }
            if (MenuTypeEnum.PAGE_TYPE.getValue().equals(menu.getType())) {
                // ’页面‘菜单
                pageMenuList.add(menuDto);
            }
            if (CommonEnum.ONE.getValue().equals(menu.getDisplay())) {
//                log.info("菜单不显示。menuId={} name={}", menu.getMenuId(), menu.getName());
                StringBuilder sb = buildFailMenuMap.computeIfAbsent(1, k -> new StringBuilder());
                sb.append(menu.getName()).append('(').append(menu.getMenuId()).append(')').append(';');
                continue;
            }

            MenuDto parentMenuDto = menuDtoMap.get(menuDto.getParentId());
            if (parentMenuDto == null) {
                if (CommonEnum.ZERO.getStringValue().equals(menuDto.getParentId())) {
                    // 如果菜单的父级菜单id为0，则默认该菜单为一级菜单
                    menuTree.add(menuDto);
                } else {
                    // 延后处理
                    lastOptMenuList.add(menuDto);
                }
                continue;
            }
            TreeSet<MenuDto> children = parentMenuDto.getChildren();
            if (children == null) {
                children = new TreeSet<>();
                parentMenuDto.setChildren(children);
            }
            children.add(menuDto);
        }
        for (MenuDto menuDto : lastOptMenuList) {
            MenuDto parentMenuDto = menuDtoMap.get(menuDto.getParentId());
            if (parentMenuDto == null) {
//                log.warn("关联不到父级菜单。{}", menuDto);
                StringBuilder sb = buildFailMenuMap.computeIfAbsent(2, k -> new StringBuilder());
                sb.append(menuDto.getName()).append('(').append(menuDto.getMenuId()).append(')').append(';');
                continue;
            }
            TreeSet<MenuDto> children = parentMenuDto.getChildren();
            if (children == null) {
                children = new TreeSet<>();
                parentMenuDto.setChildren(children);
            }
            children.add(menuDto);
        }
        log.info("菜单不显示：{}", buildFailMenuMap.get(1));
        log.info("关联不到父级菜单：{}", buildFailMenuMap.get(2));

        userMenuDto = new UserMenuDto();
        userMenuDto.setMenuTree(menuTree)
                .setPageMenuList(pageMenuList)
                .setPermitList(permitList)
                .setRoleKeyList(roleKeyList);

        // 缓存用户菜单列表数据五分钟
        redisStringHandler.set(cacheKey, userMenuDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

        return userMenuDto;
    }

    public MenuViewDto getMenuViewData(Long menuId) {
        /*
            获取菜单
         */
        MenuEntity menu = menuService.getMenu(menuId);

        MenuEntity parentMenu;
        if (menu.getParentId().equals(CommonEnum.ZERO.getLongValue())) {
            parentMenu = new MenuEntity();
            parentMenu.setName("顶级菜单");
        } else {
            /*
                获取父菜单
             */
            parentMenu = menuService.getMenu(menu.getParentId());
        }

        MenuViewDto menuViewDto = new MenuViewDto();
        menuViewDto.setParentMenuName(parentMenu.getName())
                .setType(menu.getType())
                .setName(menu.getName())
                .setPath(menu.getPath())
                .setPermit(menu.getPermit())
                .setIcon(menu.getIcon())
                .setSort(menu.getSort())
                .setDisplay(menu.getDisplay())
                .setStatus(menu.getStatus());

        // 设置枚举列表
        menuViewDto.setTypeEnumList(MenuTypeEnum.getList());
        menuViewDto.setDisplayEnumList(MenuDisplayEnum.getList());
        menuViewDto.setStatusEnumList(CommonEnum.getStatusList());

        return menuViewDto;
    }

    public TreeSet<MenuDto> finMenuTree() {

        List<MenuEntity> menuList = menuService.getMenuList(null);

        TreeSet<MenuDto> treeSet = new TreeSet<>();
        Map<String, MenuDto> menuMap = new HashMap<>();
        List<MenuDto> lastOptMenuDtoList = new ArrayList<>();

        for (MenuEntity menu : menuList) {
            MenuDto menuDto = CopyUtils.map(menu, MenuDto.class);
            menuDto.setDisabled(CommonEnum.ONE.getValue().equals(menu.getStatus()))
                    .setChildren(new TreeSet<>());

            menuMap.put(menuDto.getMenuId(), menuDto);
            if (CommonEnum.ZERO.getStringValue().equals(menuDto.getParentId())) {
                // 顶级菜单
                treeSet.add(menuDto);
                continue;
            }
            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentMenuDto)) {
                lastOptMenuDtoList.add(menuDto);
                continue;
            }
            // 添加进子列表中
            parentMenuDto.getChildren().add(menuDto);
        }
        for (MenuDto menuDto : lastOptMenuDtoList) {
            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentMenuDto)) {
                log.info("父级菜单不存在。menuId={} parentId={}", menuDto.getMenuId(), menuDto.getParentId());
                continue;
            }
            // 添加进子列表中
            parentMenuDto.getChildren().add(menuDto);
        }
        return treeSet;
    }

    public void menuChoiceExport(Long taskId, HttpServletResponse response) {
        Date start = new Date();

        /*
            获取导出任务
         */
        ExportTaskEntity exportTask = exportTaskService.get(taskId);
        if (!ExportStatusEnum.ACTIVITY.getValue().equals(exportTask.getStatus())) {
            log.info("任务状态不对：status={}", ExportStatusEnum.getName(exportTask.getStatus()));
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "菜单导出已经下载过，不可重复下载");
        }

        /*
            获取缓存参数
         */
        ChoiceExportVo choiceExportVo = JSON.parseObject(exportTask.getParams(), ChoiceExportVo.class);
        log.info("缓存参数：{}", choiceExportVo);

        /*
            获取菜单
         */
        List<MenuEntity> menuList = menuService.getList(choiceExportVo.getIdList());
        if (menuList.isEmpty()) {
            log.info("参数关联菜单数据不存在");
            throw new CzzException(ResultEnum.ILLEGAL_FAIL);
        }

        Map<Long, String> menuMap = new HashMap<>();
        List<Long> parentIdList = new ArrayList<>();

        for (MenuEntity menu : menuList) {
            menuMap.put(menu.getMenuId(), menu.getName());

            if (!menuMap.containsKey(menu.getParentId())) {
                parentIdList.add(menu.getParentId());
            }
        }

        if (!parentIdList.isEmpty()) {
            List<MenuEntity> list = menuService.getList(parentIdList);
            for (MenuEntity menu : list) {
                menuMap.put(menu.getMenuId(), menu.getName());
            }
        }
        // 菜单类型枚举映射
        Map<Integer, String> menuTypeMap = MenuTypeEnum.getMap();
        // 菜单显示状态枚举映射
        Map<Integer, String> menuDisplayMap = MenuDisplayEnum.getMap();
        // 菜单状态枚举映射
        Map<Integer, String> statusMap = CommonEnum.getStatusMap();
        // 序号
        AtomicInteger serial = new AtomicInteger(1);

        // 列表映射
        List<MenuExcelDto> menuExcelList = CopyUtils.mapList(menuList, menu -> {
            MenuExcelDto menuExcelDto = CopyUtils.map(menu, MenuExcelDto.class);
            menuExcelDto.setSerial(serial.getAndIncrement());
            menuExcelDto.setParentName(menuMap.getOrDefault(menu.getParentId(), StringUtils.EMPTY));
            menuExcelDto.setTypeName(menuTypeMap.getOrDefault(menu.getType(), StringUtils.EMPTY));
            menuExcelDto.setDisplayName(menuDisplayMap.getOrDefault(menu.getDisplay(), StringUtils.EMPTY));
            menuExcelDto.setStatusName(statusMap.getOrDefault(menu.getStatus(), StringUtils.EMPTY));
            return menuExcelDto;
        });
        String fileName = TaskTypeEnum.MENU_CHOICE_EXPORT.getName();

        ExportTaskEntity updateExportTask = new ExportTaskEntity();

        try {
            // 数据写入输出流中
            ExcelUtils.writeResponse(fileName, menuExcelList, MenuExcelDto.class, response);

            MenuExcelDto menuExcelDto = menuExcelList.get(0);
            updateExportTask.setFields(JSON.toJSONString(menuExcelDto.getExportFieldList()))
                    .setDataRow(menuExcelList.size())
                    .setStatus(ExportStatusEnum.SUCCESS.getValue());

        } catch (Exception e) {
            log.error("导出文件写入输出流异常。message={}", e.getMessage(), e);

            String errorMessage = "菜单自选数据导出执行失败";

            updateExportTask.setStatus(ExportStatusEnum.FAIL.getValue())
                    .setRemark(errorMessage);

            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            try {
                response.getWriter().println(errorMessage);
            } catch (IOException ex) {
                log.error("异常结果返回输出失败。message={}", ex.getMessage());
            }
        } finally {
            asyncService.execute(() -> {
                // 获取请求用户
                UserPo userPo = SecurityUtils.getUser();
                Date date = new Date();

                updateExportTask.setTaskId(taskId)
                        .setStartTime(start)
                        .setEndTime(date)
                        .setUpdateTime(date)
                        .setUpdateUser(userPo.getUsername())
                        .setUpdateUserId(userPo.getUserId());
                // 更新
                exportTaskService.saveExportTask(updateExportTask);
            });
        }
    }

    public void updateMenu(MenuVo menuVo) {
        // 获取正在修改菜单信息的用户id
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        /*
            获取菜单
         */
        MenuEntity menu = menuService.getMenu(menuVo.getMenuId());

        List<MenuEntity> updateMenuList = new ArrayList<>();
        List<String> menuIdList = new ArrayList<>();
        boolean isUpdateParent = !menuVo.getParentId().equals(menu.getParentId());
        if (isUpdateParent) {
            /*
                如果父级部门路径没有以父级部门主键作为结尾，则说明变更了父级部门
             */
            menuVo.setMenuPath(menuVo.getMenuPath() + menuVo.getParentId() + Constant.SPLIT);

            // 查询所有子菜单
            List<MenuEntity> menuList = menuService.getChildrenMenuList(
                    menu.getMenuPath() + menu.getMenuId() + Constant.SPLIT
            );
            if (!menuList.isEmpty()) {
                for (MenuEntity menuEntity : menuList) {
                    String menuPath = menuEntity.getMenuPath();
                    // 替换父级菜单路径
                    menuPath = menuPath.replaceFirst(menu.getMenuPath(), menuVo.getMenuPath());

                    MenuEntity updateMenu = new MenuEntity();
                    updateMenu.setMenuId(menuEntity.getMenuId())
                            .setMenuPath(menuPath)
                            .setUpdateTime(date)
                            .setUpdateUser(userPo.getUsername())
                            .setUpdateUserId(userPo.getUserId());
                    // 记录数据
                    updateMenuList.add(updateMenu);
                    // 收集菜单主键
                    menuIdList.add(menuEntity.getMenuId().toString());
                }
            }
        }

        menuIdList.add(menuVo.getMenuId().toString());
        // 删除缓存
        redisHashHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuIdList);

        /*
            修改菜单
         */
        MenuEntity updateMenu = CopyUtils.map(menuVo, MenuEntity.class);
        // 设置菜单id、设置父级菜单id、更新时间和操作更新的用户id属性值
        updateMenu.setPath(Optional.ofNullable(updateMenu.getPath()).orElse(StringUtils.EMPTY))
                .setPermit(Optional.ofNullable(updateMenu.getPermit()).orElse(StringUtils.EMPTY))
                .setIcon(Optional.ofNullable(updateMenu.getIcon()).orElse(StringUtils.EMPTY))
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());
        log.debug("更新菜单对象: {}", updateMenu);
        updateMenuList.add(updateMenu);

        // 批量更新
        menuService.batchUpdateMenu(updateMenuList);

        // 删除菜单信息的所有临时缓存数据
        this.deleteMenuCache();
        // 延时双删
        asyncService.schedule(() -> {
            // 删除缓存
            redisHashHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuIdList);
        }, 1500, TimeUnit.MILLISECONDS);

    }

    public void updateMenuStatus(UpdateStatusVo updateStatusVo) {
        // 获取正在修改菜单信息的用户
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        Long menuId = updateStatusVo.getIdList().get(0);
        Integer status = updateStatusVo.getStatus();

        // 删除缓存
        String hashKey = menuId.toString();
        redisHashHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), hashKey);

        /*
            更新菜单状态
         */
        MenuEntity updateMenu = new MenuEntity();
        updateMenu.setMenuId(menuId)
                .setStatus(status)
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getUserId());
        menuService.saveOrEdit(updateMenu);

        // 删除菜单信息的所有临时缓存数据
        this.deleteMenuCache();
        // 延时双删
        asyncService.schedule(() -> {
            // 删除缓存
            redisHashHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), hashKey);
        }, 1500, TimeUnit.MILLISECONDS);
    }

    public void deleteMenu(Long menuId) {

        /*
            删除菜单
         */
        menuService.deleteMenu(menuId);

        // 删除Redis中保存的菜单信息
        redisHashHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuId.toString());

        // 删除菜单信息相关的所有临时缓存数据
        this.deleteMenuCache();

        /*
            异步删除菜单关联数据
         */
        asyncService.execute(() -> {
            // 删除菜单关联角色数据
            roleMenuService.delete(null, Collections.singletonList(menuId));
        });
    }

    /**
     * 获取菜单实体类对象
     * @param menuVo 菜单参数对象
     * @param userPo 操作用户
     * @param date   日期对象
     * @return 菜单实体类对象
     */
    private MenuEntity getMenuEntity(MenuVo menuVo, UserPo userPo, Date date) {
        MenuEntity menu = new MenuEntity();
        menu.setMenuId(menuVo.getMenuId());
        menu.setParentId(menuVo.getParentId());
        menu.setName(menuVo.getName());
        menu.setType(menuVo.getType());
        if (MenuTypeEnum.PACKAGE_TYPE.getValue().equals(menuVo.getType())) {
            // 目录 菜单
            menu.setPath(StringUtils.EMPTY);
            menu.setIcon(menuVo.getIcon());
            menu.setPermit(StringUtils.EMPTY);
        } else if (MenuTypeEnum.PAGE_TYPE.getValue().equals(menuVo.getType())) {
            // 页面 菜单
            menu.setPath(menuVo.getPath());
            menu.setIcon(menuVo.getIcon());
            menu.setPermit(StringUtils.EMPTY);
        } else if (MenuTypeEnum.BUTTON_TYPE.getValue().equals(menuVo.getType())) {
            // 按钮 菜单
            menu.setPath(StringUtils.EMPTY);
            menu.setIcon(StringUtils.EMPTY);
            menu.setPermit(menuVo.getPermit());
        }
        menu.setSort(menuVo.getSort());
        menu.setStatus(CommonEnum.ZERO.getValue());
        menu.setDisplay(menuVo.getDisplay());
        menu.setDeleteStatus(CommonEnum.ZERO.getValue());
        menu.setUpdateTime(date);
        menu.setUpdateUser(StringUtils.EMPTY);
        menu.setUpdateUserId(CommonEnum.ZERO.getLongValue());
        menu.setCreateTime(date);
        menu.setCreateUser(userPo.getUsername());
        menu.setCreateUserId(userPo.getUserId());

        return menu;
    }

    /**
     * 删除菜单信息相关的所有临时缓存数据
     */
    private void deleteMenuCache() {
        String[] patternKeys = {
                RedisConstant.STRING + EntityConstant.MENU + Constant.UNDER + RedisConstant.LIST
                        + RedisConstant.ASTERISK,
                RedisConstant.STRING + EntityConstant.MENU + Constant.UNDER + RedisConstant.TREE
                        + RedisConstant.ASTERISK,
                RedisConstant.STRING + EntityConstant.USER + Constant.UNDER + EntityConstant.MENU
                        + Constant.SPLIT + RedisConstant.TREE + RedisConstant.ASTERISK
        };
        redisHandler.fuzzyDelete(patternKeys);
    }

}
